
#include <stdio.h>
#include <math.h>
#include <GL/glut.h>
#include "camera.h"

/////	This returns a perpendicular vector from 2 given vectors by taking the cross product.											
CVector3 Cross(CVector3 vVector1, CVector3 vVector2)
{
	CVector3 vNormal;	

	// Calculate the cross product with the non communitive equation
	vNormal.x = ((vVector1.y * vVector2.z) - (vVector1.z * vVector2.y));
	vNormal.y = ((vVector1.z * vVector2.x) - (vVector1.x * vVector2.z));
	vNormal.z = ((vVector1.x * vVector2.y) - (vVector1.y * vVector2.x));

	// Return the cross product
	return vNormal;										 
}

/////	This returns the magnitude of a vector
float Magnitude(CVector3 vNormal)
{
	// Here is the equation:  magnitude = sqrt(V.x^2 + V.y^2 + V.z^2) : Where V is the vector
   return (float)sqrt( (vNormal.x * vNormal.x) + (vNormal.y * vNormal.y) + (vNormal.z * vNormal.z) );
}

/////	This returns a normalize vector (A vector exactly of length 1)
CVector3 Normalize(CVector3 vVector)
{
	// Get the magnitude of our normal
	float magnitude = Magnitude(vVector);				

	// Now that we have the magnitude, we can divide our vector by that magnitude.
	// That will make our vector a total length of 1.  
	vVector = vVector / magnitude;		
	
	// Finally, return our normalized vector
	return vVector;										
}

/////	This is the class constructor
CCamera::CCamera()
{
	CVector3 vZero = CVector3(0.0, 0.0, 0.0);		// Init a vector to 0 0 0 for our position
	CVector3 vView = CVector3(0.0, 1.0, 0.5);		// Init a starting view vector (looking up and out the screen) 
	CVector3 vUp   = CVector3(0.0, 0.0, 1.0);		// Init a standard up vector (Rarely ever changes)

	m_vPosition	= vZero;					// Init the position to zero
	m_vView		= vView;					// Init the view to a std starting view
	m_vUpVector	= vUp;					// Init the UpVector
}

/////	This function sets the camera's position and view and up vector.
void CCamera::PositionCamera( float positionX, float positionY, float positionZ,
				  		            float viewX,     float viewY,     float viewZ,
							         float upVectorX, float upVectorY, float upVectorZ)
{
	CVector3 vPosition = CVector3(positionX, positionY, positionZ);
	CVector3 vView		 = CVector3(viewX, viewY, viewZ);
	CVector3 vUpVector = CVector3(upVectorX, upVectorY, upVectorZ);

	// The code above just makes it cleaner to set the variables.
	// Otherwise we would have to set each variable x y and z.
	m_vPosition = vPosition; // Assign the position
	m_vView     = vView;		 // Assign the view
	m_vUpVector = vUpVector; // Assign the up vector
}

/////	This allows us to look around using the mouse, like in most first person games.
void CCamera::SetViewByMouse()
{
	int mousePosX, mousePosY;
	int middleX = 800  >> 1;			// This is a binary shift to get half the width
 	int middleY = 600 >> 1;				// This is a binary shift to get half the height
	float angleY = 0.0f;					// This is the direction for looking up or down
	float angleZ = 0.0f;					// This will be the value we need to rotate around the Y axis (Left and Right)
	static float currentRotX = 0.0f;
	
	// Get the mouse's current X,Y position
	//GetCursorPos(&mousePos);						
	this->GetCursorPosition(&mousePosX, &mousePosY);						
		
	// If our cursor is still in the middle, we never moved... so don't update the screen
	if( (mousePosX == middleX) && (mousePosY == middleY) ) return;

	// Set the mouse position to the middle of our window
	this->SetCursorPosition(middleX, middleY);											

	// Get the direction the mouse moved in, but bring the number down to a reasonable amount
	angleY = (float)( mousePosX ) / 500.0f;		
	angleZ = (float)( mousePosY ) / 500.0f;		

	// Here we keep track of the current rotation (for up and down) so that
	// we can restrict the camera from doing a full 360 loop.
	currentRotX -= angleZ;  

	// If the current rotation (in radians) is greater than 1.0, we want to cap it.
	if(currentRotX > 1.0f)
		currentRotX = 1.0f;
	// Check if the rotation is below -1.0, if so we want to make sure it doesn't continue
	else if(currentRotX < -1.0f)
		currentRotX = -1.0f;
	// Otherwise, we can rotate the view around our position
	else
	{
		// To find the axis we need to rotate around for up and down
		// movements, we need to get a perpendicular vector from the
		// camera's view vector and up vector.  This will be the axis.
		CVector3 vAxis = Cross(m_vView - m_vPosition, m_vUpVector);
		vAxis = Normalize(vAxis);

		// Rotate around our perpendicular axis and along the y-axis
		RotateView(angleZ, vAxis.x, vAxis.y, vAxis.z);
		RotateView(angleY, 0, 1, 0);
	}
}

/////	This rotates the view around the position using an axis-angle rotation
void CCamera::RotateView(float angle, float x, float y, float z)
{
	CVector3 vNewView;

	// Get the view vector (The direction we are facing)
	CVector3 vView = m_vView - m_vPosition;		

	// Calculate the sine and cosine of the angle once
	float cosTheta = (float)cos(angle);
	float sinTheta = (float)sin(angle);

	// Find the new x position for the new rotated point
	vNewView.x  = (cosTheta + (1 - cosTheta) * x * x)		* vView.x;
	vNewView.x += ((1 - cosTheta) * x * y - z * sinTheta)	* vView.y;
	vNewView.x += ((1 - cosTheta) * x * z + y * sinTheta)	* vView.z;

	// Find the new y position for the new rotated point
	vNewView.y  = ((1 - cosTheta) * x * y + z * sinTheta)	* vView.x;
	vNewView.y += (cosTheta + (1 - cosTheta) * y * y)		* vView.y;
	vNewView.y += ((1 - cosTheta) * y * z - x * sinTheta)	* vView.z;

	// Find the new z position for the new rotated point
	vNewView.z  = ((1 - cosTheta) * x * z - y * sinTheta)	* vView.x;
	vNewView.z += ((1 - cosTheta) * y * z + x * sinTheta)	* vView.y;
	vNewView.z += (cosTheta + (1 - cosTheta) * z * z)		* vView.z;

	// Now we just add the newly rotated vector to our position to set
	// our new rotated view of our camera.
	m_vView = m_vPosition + vNewView;
}

/////	This strafes the camera left and right depending on the speed (-/+)
void CCamera::StrafeCamera(float speed)
{	
	// Add the strafe vector to our position
	m_vPosition.x += m_vStrafe.x * speed;
	m_vPosition.z += m_vStrafe.z * speed;

	// Add the strafe vector to our view
	m_vView.x += m_vStrafe.x * speed;
	m_vView.z += m_vStrafe.z * speed;
}

/////	This will move the camera forward or backward depending on the speed
void CCamera::MoveCamera(float speed)
{
   CVector3 vVector = m_vView - m_vPosition;
	vVector = Normalize(vVector);
	
	m_vPosition.x += vVector.x * speed;		// Add our acceleration to our position's X
	m_vPosition.z += vVector.z * speed;		// Add our acceleration to our position's Z
	m_vView.x += vVector.x * speed;			// Add our acceleration to our view's X
	m_vView.z += vVector.z * speed;			// Add our acceleration to our view's Z	
}

/////	This updates the camera's view and strafe vector
void CCamera::Update() 
{
	// Initialize a variable for the cross product result
	CVector3 vCross = Cross(m_vView - m_vPosition, m_vUpVector);
	// Normalize the strafe vector
	m_vStrafe = Normalize(vCross);
	// Move the camera's view by the mouse
	SetViewByMouse();
}

/////	This updates the camera according to the 
void CCamera::Look()
{
	// Give openGL our camera position, then camera view, then camera up vector
	gluLookAt(m_vPosition.x, m_vPosition.y, m_vPosition.z,	
			    m_vView.x,	    m_vView.y,     m_vView.z,	
			    m_vUpVector.x, m_vUpVector.y, m_vUpVector.z);
}

void CCamera::SetCursorPosition(int  x, int  y)
{
	this->mousePosX = x;
	this->mousePosY = y;
}

void CCamera::GetCursorPosition(int *x, int *y)
{
	*x = this->mousePosX;
	*y = this->mousePosY;
}

void CCamera::SetWindowSize(int  x, int  y)
{
	this->windowSizeX = x;
	this->windowSizeY = y;
}

void CCamera::GetWindowSize(int *x, int *y)
{
	*x = this->windowSizeX;
	*y = this->windowSizeY;
}

